#include "stdafx.h"
#include "ScriptEditorSolution.h"
#include "ScriptEditorSolutionSerialiser.h"
#include "ScriptEditorSolutionTree.h"
#include "ScriptEditorWidget.h"
#include "ScriptEditorTabWidget.h"
#include "ScriptEditorCompiler.h"
#include "ScriptLogOutput.h"
#include <LitFilePath.h>
#include <qtreewidget.h>
#include <LitEditorFileDialog.h>
#include <LitEditorDialog.h>
#include <LitEditorInstance.h>
#include <qmessagebox.h>
#include <LitNameGenerator.h>

namespace Lit
{
	namespace Editor
	{
		ScriptSolution::ScriptSolution( ScriptSolutionTree* tree, ScriptTabWidgetPtr tabs, InstancePtr inst )
			: mTabs( tabs )
			, mTree( tree ), mCreated(false)
			, mInstance( inst )
			, mSettingsDlg( tree )
			, mEntryPoint( "void main()" )
			, mRenderEntryPoint( "void frame_update( Ogre::Real )" )
		{
			mCompiler = new ScriptCompiler( inst );
			mCompiler->setBuildErrorCallback( boost::bind( &ScriptSolution::buildError, this, _1, _2 ) );
			mCompiler->setBuildErrorCallback( boost::bind( &ScriptSolution::buildSuccess, this, _1 ) );
			
			tabs->setEditorInstance( inst );
			tabs->setExtension("cpp");

			Ogre::LogManager::getSingleton().setDefaultLog( *ScriptEngine::getLog() );
			
			connect( *tabs, SIGNAL(tabCloseRequested(int)), this, SLOT(tabCloseRequested(int)) );
			connect( mTree, SIGNAL(scriptDoubleClicked(ScriptSolutionItemPtr)), this, SLOT(openScriptEditor(ScriptSolutionItemPtr)) );
			connect( mTree, SIGNAL(scriptRightClicked(ScriptSolutionItemPtr)), this, SLOT(showScriptContextMenu(ScriptSolutionItemPtr)) );
			if( Ogre::Root::getSingletonPtr() ) Ogre::Root::getSingletonPtr()->addFrameListener( this );
			
		}
		ScriptSolution::~ScriptSolution(){
			if( Ogre::Root::getSingletonPtr() ) Ogre::Root::getSingletonPtr()->removeFrameListener( this );
			clearItems();
			mCompiler.destroy();
			//ScriptEngine::getLog()->removeListener( *mCompiler->getLog() );
		}
		void ScriptSolution::openScriptEditor( ScriptSolutionItemPtr item ){ if( item ) item->openEditor(); }
		void ScriptSolution::closeScriptEditor( ScriptSolutionItemPtr item ){ if( item ) item->closeEditor(); }
		void ScriptSolution::showScriptContextMenu( ScriptSolutionItemPtr item )
		{

		}
				
			
		bool ScriptSolution::hasItem( const ScriptSolutionItem::WidgetPtr& win ) const{	for( auto i=mScripts.begin(); i!=mScripts.end(); i++ ){ if( i->get()->getEditor() == win ) return true;	} return false;	}
		bool ScriptSolution::hasItem( const ScriptSolutionItem::NodePtr& nod ) const{	for( auto i=mScripts.begin(); i!=mScripts.end(); i++ ){ if( i->get()->getNode() == nod ) return true;	} return false;	}
		bool ScriptSolution::hasItem( const ScriptSolutionItemPtr& itm ) const{	for( auto i=mScripts.begin(); i!=mScripts.end(); i++ ){ if( *i == itm ) return true;	} return false;	}
			
		ScriptSolutionItemPtr ScriptSolution::findItem( const ScriptSolutionItem::WidgetPtr& win ) const{ for( auto i=mScripts.begin(); i!=mScripts.end(); i++ ){ if( i->get()->getEditor() == win ) return *i;	} return NULL; }
		ScriptSolutionItemPtr ScriptSolution::findItem( const ScriptSolutionItem::NodePtr& nod ) const{ for( auto i=mScripts.begin(); i!=mScripts.end(); i++ ){ if( i->get()->getNode() == nod ) return *i;	} return NULL; }
		ScriptSolutionItemPtr ScriptSolution::findItem( const ScriptSolutionItemPtr& itm ) const{ for( auto i=mScripts.begin(); i!=mScripts.end(); i++ ){ if( *i == itm ) return *i;	} return NULL; }
			
		ScriptSolutionItemPtr ScriptSolution::addItem( ScriptSolutionItem::NodePtr node, ScriptSolutionItem::WidgetPtr win )
		{
			ScriptSolutionItemPtr item;
			if( hasItem( win ) ) return findItem( node );
			else mScripts.push_back( (item=new ScriptSolutionItem( this, node, win )) );
			
			connect( *win, SIGNAL(modifiedStateChanged(ScriptEditorWidget*,bool)), this, SLOT(modifiedStateChanged(ScriptEditorWidget*, bool)) );
			
			return item;
		}
		bool ScriptSolution::removeItem( ScriptSolutionItemPtr &item )
		{
			for( auto i=mScripts.begin(); i!=mScripts.end(); i++ ){ if( *i == item ){ mScripts.erase( i ); break; } }
			if( item )
			{
				item.destroy();
			}
			return true;
		}

		void ScriptSolution::clearItems()
		{
			for( auto i=mScripts.begin(); i!=mScripts.end(); i++ ){ i->destroy();	}
			mScripts.clear();
		}
		void ScriptSolution::buildError( ScriptModulePtr mod, const std::string &err )
		{
			mInstance->getLogger()->logMessage("Build errors in "+mod->getName());
		}
		void ScriptSolution::buildSuccess( ScriptModulePtr mod )
		{
			mInstance->getLogger()->logMessage("Module "+mod->getName()+" successfully built");
		}

		void ScriptSolution::showSettings()
		{
			mSettingsDlg.exec( this );
		}

		bool ScriptSolution::frameRenderingQueued( const Ogre::FrameEvent &evt )
		{
			/*for( auto i=mRenderScripts.begin(); i!=mRenderScripts.end(); i++ )
			{
				try
				{
					mRenderModule = mCompiler->getEngine()->createModule( "_render_", SMF_ALWAYS_CREATE );
					int r = mRenderModule->addSection( i->get()->getName(), i->get()->getText() );
					assert( mRenderModule->build()>=0 );
					ScriptContextPtr ctx(mCompiler->getEngine()->nextFreeContext());
					ScriptFunctionPtr render_entry_point(mRenderModule->data()->GetFunctionByDecl( mRenderEntryPoint.c_str() ));
					if( !render_entry_point ) throw ScriptNoEntryPoint( mRenderEntryPoint, mRenderEntryPoint, 0, i->get()->getName() );
					assert( ctx->prepare( render_entry_point ) >= 0 );
					Lit::ScriptContextState state = ctx->execute();
					ctx->release();
					mCompiler->getEngine()->releaseModule( mRenderModule );
				}
				catch( std::exception & e ){
					Dialog::warn( mTree, "A render script caused an error !", "Render Script ["+i->get()->getName()+"]: "+e.what() );
				}
				catch( ... ){
					Dialog::warn( mTree, "A render script caused an error !", "Render Script ["+i->get()->getName()+"]: unknown error" );
				}
			}*/
			return true;
		}
		void ScriptSolution::tabCloseRequested ( int index ){
			QWidget * w( mTabs->widget(index) );
			ScriptSolutionItemPtr item(findItem((ScriptEditorWidget*)w));
			closeScriptEditor( item );
		}
		ScriptSolutionItemPtr ScriptSolution::getCurrentScript() const
		{
			for( auto i=mScripts.begin(); i!=mScripts.end(); i++ ) if( i->get()->isCurrent() ) return *i;
			return NULL;
		}

		bool ScriptSolution::isCreated() const { return mCreated; }
		void ScriptSolution::modifiedStateChanged(ScriptEditorWidget *widget, bool state)
		{
			if( !widget ) return;
			
			if( state ) mTabs->setTabText( widget->getId(), QString( widget->getName().c_str() ) + " *" );
			else mTabs->setTabText( widget->getId(), QString( widget->getName().c_str() ) );
		}

		/****************************************************************************************************************
		*	prepareUi:
		************************************************************************************************************************/
		void ScriptSolution::prepareUi( const std::string &name )
		{
			if(!mName.empty())
				mCompiler->getEngine()->removeIncludeSearchPath( getSolutionDir() );

			mName = name;
			mTabs->clear();
			mTabs->setTabsClosable(true);
			mTree->newSolution( mName );
			mCreated = true;

			mCompiler->getEngine()->addIncludeSearchPath( getSolutionDir() );
		}

		/****************************************************************************************************************
		*	newSolution:
		************************************************************************************************************************/
		void ScriptSolution::newSolution() {
			StringReturn ret;
			while( ( ret = Dialog::ask(mTree, "Solution Name", "Name of the solution", "new solution" ) ).accepted() && boost::filesystem::exists( ScriptUserPath().str()+"/"+ret.value ) )
			{ 
				int ans = QMessageBox::warning( mTree, "Error", "Solution '"+QString(ret.value.c_str())+"' already exists, do you want to overwrite it ?", QMessageBox::Yes|QMessageBox::No|QMessageBox::Cancel );
				if( ans == QMessageBox::Yes )
				{
					boost::filesystem::remove_all( ScriptUserPath().str()+"/"+ret.value );
					break;
				}
				else if( ans == QMessageBox::No )
				{
				}
				else if( ans == QMessageBox::Cancel )
				{
					return;
				}
			}
			if( ret.accepted() )
			{
				prepareUi(ret.value);
				Lit::FileSystem::createDirectories( ScriptUserPath()+"/"+mName );
				createScript( "main" );
			}
		}

		/****************************************************************************************************************
		*	openSolution:
		************************************************************************************************************************/
		void ScriptSolution::openSolution(){
			FilePath np( FileDialog::getOpenFileName( NULL, "Open Solution", ScriptUserPath(), "All Script Solution Files (*.ssol);;" ) );
			if( np.str().empty() ) return;
			if( isCreated() )
				closeSolution();
			mSavePath = np;
			_importSol( mSavePath );
		}

		/****************************************************************************************************************
		*	saveSolution:
		************************************************************************************************************************/
		void ScriptSolution::saveSolution( bool as ){
			FilePath np = ScriptUserPath()+"/"+mName+"/"+mName+".ssol";
			if( as ) np=FileDialog::getSaveFileName( NULL, "Save Solution", ScriptUserPath(), "All Script Solution Files (*.ssol);;" );
			if( np.str().empty() ) return;
			mSavePath = np;
			mTabs->saveAllUnsavedScripts();
			_exportSol( mSavePath );
		}

		/****************************************************************************************************************
		*	closeSolution:
		************************************************************************************************************************/
		bool ScriptSolution::closeSolution()
		{
			bool sav = false;
			for( auto i=mScripts.begin(); i!=mScripts.end(); i++ )
				if( i->get()->isModified() ){ sav = true; break; }
			if( sav )
			{
				int ans = QMessageBox::question( mTree, "Save Changes ?", "Some changes has been detected, do you want to save before closing the solution ?", QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel );
				if( ans == QMessageBox::Yes )
				{
					for( auto i=mScripts.begin(); i!=mScripts.end(); i++ )
						if( i->get()->isModified() ) i->get()->save();
				}
				else if( ans == QMessageBox::Cancel )
				{
					return false;
				}
			}
			mTabs->clear();
			mTree->clear();
			mCreated = false;
			return true;
		}
		void ScriptSolution::compileByteCode()
		{
			ScriptEditorWidgetPtr main;
			std::vector<ScriptEditorWidgetPtr> vec;
			for( auto i=mScripts.begin(); i!=mScripts.end(); i++ )
			{
				if( i->get()->getName() == "main.cpp" )
				{
					main = i->get()->getEditor();
				}
				else vec.push_back( i->get()->getEditor() );
			}
			if( main ) vec.push_back(main);

			ScriptModulePtr module(mCompiler->getEngine()->createModule( mName, SMF_CREATE_IF_NOT_EXISTS ));
			try
			{
				//module->setIncludeCallback( &ScriptSolution::IncludeCallback, this );
				mCompiler->compile( module,vec, true );
				std::string out = FileDialog::getSaveFileName( NULL, "Save Compiled ByteCode", "./", "All Binary Files (*.bin);;" );
				if( !out.empty() )
				{
					ScriptStreamPtr strm( new ScriptStream ( out, Stream::BinaryWrite ) );
#ifndef NDEBUG
					module->data()->SaveByteCode( *strm, false );
#else
					module->data()->SaveByteCode( *strm, true );
#endif
					strm.destroy();
				}
			}
			catch( std::exception & e )
			{
				mTabs->getEditorInstance()->getLogger()->logMessage(std::string("\t")+e.what());
			}
			catch( ... ){ mTabs->getEditorInstance()->getLogger()->logMessage("\tunknown error"); }
			mCompiler->getEngine()->releaseModule( module );
		}
		void ScriptSolution::compile()
		{
			saveSolution();
			std::vector<ScriptEditorWidgetPtr> vec;
			for( auto i=mScripts.begin(); i!=mScripts.end(); i++ )
			{
				vec.push_back( i->get()->getEditor() );
			}
			ScriptModulePtr module(mCompiler->getEngine()->createModule( mName, SMF_ALWAYS_CREATE));
			try
			{
				//module->setIncludeCallback( &ScriptSolution::IncludeCallback, this );
				mCompiler->compile( module,vec, true );
			}
			catch( std::exception & e )
			{
				mTabs->getEditorInstance()->getLogger()->logMessage(std::string("\t")+e.what());
			}
			catch( ... ){ mTabs->getEditorInstance()->getLogger()->logMessage("\tunknown error"); }
			mCompiler->getEngine()->releaseModule( module );
		}
		void ScriptSolution::run()
		{
			saveSolution();
			std::vector<ScriptEditorWidgetPtr> vec;
			for( auto i=mScripts.begin(); i!=mScripts.end(); i++ )
			{
				vec.push_back( i->get()->getEditor() );
			}
			mCompiler->run( mName, mEntryPoint, vec );
		}
		void ScriptSolution::_importSol( const FilePath &p ){
			StreamPtr strm( new FileStream( p.str(), Stream::TextRead ) );
			ScriptSolutionSerialiser().fromStream( strm, *this );
			strm.destroy();
		}
		void ScriptSolution::_exportSol( const FilePath &p ){
			boost::filesystem::remove( p.str() );
			StreamPtr strm( new FileStream( p.str(), Stream::TextWrite ) );
			ScriptSolutionSerialiser().toStream( strm, *this );
			strm.destroy();
		}
		ScriptSolutionItemPtr ScriptSolution::openScript(const std::string &path){
			try
			{
				ScriptSolutionItemPtr item = addItem( mTree->addFile( FilePath(path).fileName() ), mTabs->addScript(path) ); 
				return item;
			}
			catch( std::exception & e )
			{
				QMessageBox::warning(mTree,"Error", e.what());
			}
			return NULL;
		}
		std::string ScriptSolution::generateScriptName(){
			NameGenerator nmr("new script", "cpp");
			while( hasScript( nmr.peek() ) ) nmr.generate();
			return nmr.peek(false);
		}
		ScriptSolutionItemPtr ScriptSolution::createScript( const std::string &base_name, size_t flags ){
			try
			{
				ScriptSolutionItemPtr item = addItem( mTree->addFile( base_name ), mTabs->createScript( base_name.empty()?generateScriptName():base_name, "// New Script File\n\nvoid main()\n{\n\t\n}\n" ) );
				
				item->setFlags( flags );
				item->setPath( getSolutionDir()+"/"+item->getName() );
				item->save();
				return NULL;
			}
			catch( std::exception & e )
			{
				QMessageBox::warning(mTree,"Error", e.what());
			}
			return NULL;
		}
		bool ScriptSolution::hasScript( const std::string &name ) const{ 
			for( auto i=mScripts.begin(); i!=mScripts.end(); i++ )
				if( i->get()->getPath().fileName() == name ) return true;
			return false;
		}
		bool ScriptSolution::hasScript( const ScriptSolutionItemPtr &itm ) const{ return hasItem( itm ); }
		bool ScriptSolution::hasScript( const ScriptSolutionItem::NodePtr &node ) const{ return hasItem(node); }
		bool ScriptSolution::hasScript( const ScriptSolutionItem::WidgetPtr &widget ) const{ return hasItem( widget ); }
		ScriptSolutionItemPtr ScriptSolution::findScript( const std::string &name ) const
		{
			for( auto i=mScripts.begin(); i!=mScripts.end(); i++ ) if( i->get()->getName() == name ) return *i;
			return NULL;
		}
		ScriptSolutionItemPtr ScriptSolution::findScript( const ScriptSolutionItem::NodePtr &node ) const{ return findItem( node ); }
		ScriptSolutionItemPtr ScriptSolution::findScript( const ScriptSolutionItem::WidgetPtr &widget ) const{ return findItem(widget); }

		const ScriptCompilerPtr& ScriptSolution::getCompiler() const{return mCompiler;}
		const ScriptTabWidgetPtr& ScriptSolution::getTabs() const{return mTabs;}

		const ScriptSolution::ScriptList&	ScriptSolution::getScripts() const{return mScripts;}
		ScriptSolution::ScriptList&			ScriptSolution::getScripts(){return mScripts;}
		
		ScriptSolution::ScriptList		ScriptSolution::getRenderScripts() const
		{
			ScriptList lst;
			for( auto i=mScripts.begin(); i!=mScripts.end(); i++ ) if( i->get()->testFlag( ScriptSolutionItem::ItemIsRender ) ) lst.push_back( *i );
			return lst;
		}

		ScriptSolutionTree* ScriptSolution::getTreeWidget(){return mTree;}
		const FilePath& ScriptSolution::getSavePath() const{return mSavePath;}
		bool ScriptSolution::isSavePathDefined() const{return !mSavePath.str().empty();}
	};
};